import bus from '../../dxmodules/dxEventBus.js';
import common from '../../dxmodules/dxCommon.js';
import config from '../../dxmodules/dxConfig.js'
import driver from '../driver.js';
import dxNtp from '../../dxmodules/dxNtp.js'
import utils from '../common/utils/utils.js';
import safeService from '../service/safeService.js'
import log from '../../dxmodules/dxLogger.js'
import dxMap from '../../dxmodules/dxMap.js'
import std from '../../dxmodules/dxStd.js'
import tz from "../../dxmodules/dxTimeZones.js";

const configService = {}
// Matches IP addresses represented in dotted decimal notation, such as 192.168.0.1.
const ipCheck = v => /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(v)
// Matches the format 192.168.0.1:8080
const ipCheckWithPort = v => /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\:\d{1,5}$/.test(v);
// Port restriction up to 65535
const port = v => /^(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/.test(v)
// non-negative integers
const regnCheck = v => /^(0|[1-9]\d*)$/.test(v)
// The validation rules for all supported configuration items and the callback after successful setup.
const supported = {
    sysInfo: {
        //Work mode
        w_mode: { rule: v => [1, 2].includes(v) },
        //RS232 or RS485 parameters
        p_uart1: { rule: v => typeof v == 'string' },
        //ttl parameter	
        p_uart2: { rule: v => typeof v == 'string' },
        //Wiegand Busy delay
        wiegand_busy_delay: { rule: regnCheck },
        //Wiegand free	delay
        wiegand_free_delay: { rule: regnCheck },
        //NFC data output port selection in normal mode	
        nochannel: { rule: regnCheck },
        //NFC data output port selection in development mode
        ndchannel: { rule: regnCheck },
        //Normal mode QR code password data output port selection	
        ochannel: { rule: regnCheck },
        //Select the output port for QR code password data in development mode
        dchannel: { rule: regnCheck },
        //Bluetooth data output port selection in normal mode
        blochannel: { rule: regnCheck },
        //Bluetooth data output port selection in development mode
        bldchannel: { rule: regnCheck },
        //HTTP server address
        haddr: { rule: v => typeof v == 'string' },
        //HTTP timeout period s
        houttime: {
            rule: v => {
                return regnCheck(v) && v <= 5
            }
        },
        //Choose between Wi-Fi or Ethernet output port
        owifi: { rule: regnCheck },
        //Successful network transmission behavior	
        awifi_s: { rule: regnCheck },
        //Relay delay milliseconds
        relayd: { rule: regnCheck, callback: v => safeService.updateRelayd(v) },
        //Network transmission failure behavior
        awifi_f: { rule: regnCheck },
        //TCP service address
        taddr: { rule: ipCheck },
        //TCP port
        port: { rule: regnCheck },
        //TCP timeout duration (s)
        touttime: {
            rule: v => {
                return regnCheck(v) && v <= 5
            }
        },
        //QR code switch: 1 on, 0 off
        codeSwitch: { rule: v => [0, 1].includes(v) },
        ST: { rule: regnCheck },
        A1: { rule: regnCheck },
        B1: { rule: regnCheck },
        C1: { rule: regnCheck },
        D1: { rule: regnCheck },
        E1: { rule: regnCheck },
        F1: { rule: regnCheck },
        G1: { rule: regnCheck },
        H1: { rule: regnCheck },
        I1: { rule: regnCheck },
        J1: { rule: regnCheck },
        K1: { rule: regnCheck },
        //Prefix and suffix format 1 is char, 2 is hex
        chorc: { rule: v => [1, 2].includes(v) },
        //prefix
        prefix: { rule: v => typeof v != 'number' },
        //postfix
        postfix: { rule: v => typeof v != 'number' },
        //QR code output format
        ft: { rule: regnCheck },
        //Add a newline character: 0 is not added, 1 is added.
        nl: { rule: v => [0, 1].includes(v) },
        //Add Enter (0 - no Enter, 1 - enter)
        cr: { rule: v => [0, 1].includes(v) },
        //Action after scanning (bit control)
        ascan: { rule: regnCheck },
        //Buzzer delay (ms)
        beepd: {
            rule: (v) => {
                return regnCheck(v) && v <= 1000
            }
        },
        //Flash delay
        ledd: { rule: regnCheck },
        //The code system selection is based on the number of bits. Select all: 64511
        de_type: { rule: regnCheck },
        //Scanning mode: 0 for interval, 1 for single scan.
        s_mode: { rule: v => [0, 1].includes(v) },
        //Interval Effective Time
        interval: { rule: v => /^(500|[5-9]\d{2,}|[1-9]\d{3,})$/.test(v) },
        //Timeout period of the retrieval code	
        search_timeout: { rule: regnCheck },
        //Decoding engine type	
        decoder: { rule: regnCheck },
        //Decoding timeout	
        decoder_timeout: { rule: regnCheck },
        //Decoding engine corresponding strategy	
        search_mode: { rule: regnCheck },
        //Decoding engine feature configuration	
        decoder_mode: { rule: regnCheck },
        //QR code parameter configuration
        qr_mode: { rule: regnCheck },
        //Delay between two decoding operations
        decoder_delay: { rule: regnCheck },
        //OCR mode
        ocr_mode: { rule: regnCheck },
        //OCR recognition template
        ocr_template: { rule: v => typeof v == 'string' },
        //Network type: 1 Ethernet; 0 Network off.
        net_type: { rule: v => [0, 1, 2, 4].includes(v) },
        //MAC address: 0 (default), 2 (manual setting)
        fixed_macaddr_enable: { rule: v => [0, 1, 2].includes(v) },
        //MAC address can be set manually.
        macaddr: { rule: v => typeof v == 'string' },
        //0 Dynamic 1 Static
        ip_mode: { rule: v => [0, 1].includes(v) },
        ip: { rule: ipCheck },
        gateway: { rule: ipCheck },
        dns: { rule: v => !v.split(",").some(ip => !ipCheck(ip)) },
        mask: { rule: ipCheck },
        //NFC 1 on 0 off
        nfc: { rule: v => [0, 1].includes(v) },
        //Configure whether to read the physical ID card number: 0 on, 1 off.
        nfc_identity_card_enable: { rule: v => [0, 1].includes(v) },
        // NFC supported protocols: bit0: ISO14443-A 106kbps; bit1: ISO14443-B 106kbps; bit3: ISO15693
        nfc_card_protocol: { rule: regnCheck },
        // start bit
        st: { rule: regnCheck },
        // Output length
        len: { rule: regnCheck },
        // 0: Transparent transmission (e.g., card number: 9384ED18 => 93 84 ED 18, four bytes), 1: Hexadecimal output (each digit of the card number is represented in ASCII hexadecimal, eight bytes), 2: Decimal output (the card number is converted from a number to a decimal number, each digit is represented in ASCII hexadecimal, number of bytes is variable).
        nft: { rule: regnCheck },
        //Actions after swiping the card
        anfc: { rule: regnCheck },
        // Prefix, char (each character is represented in ASCII hexadecimal), hex (must be even and each bit must be between 0 and f).
        pri: { rule: v => typeof v != 'number' },
        // Suffix, same as prefix
        pos: { rule: v => typeof v != 'number' },
        // 0: string, 1: hexadecimal
        horc: { rule: v => [0, 1].includes(v) },
        // Whether to add a carriage return (0D) after the output NFC data, 0: no carriage return, 1: add carriage return.
        nnl: { rule: v => [0, 1].includes(v) },
        // Whether to add a newline character (0A, after 0D) to the output NFC data: 0: no newline, 1: add newline.
        ncr: { rule: v => [0, 1].includes(v) },
        // 0: Output NFC data is in ascending order (card number is in ascending order); 1: Output NFC data is in descending order, which is determined first.
        ord: { rule: v => [0, 1].includes(v) },
        // ID card forward or reverse order is not currently supported.
        idord: { rule: v => [0, 1].includes(v) },
        //The default output is 0 (no output). Other outputs are determined by the bit positions.
        nfc_otag: { rule: regnCheck },
        //ID card data format: 0 is ASCII, 2 is JSON format.
        online_id_format: { rule: regnCheck },
        //Bluetooth prefix and suffix: 1 char 2 hex
        blhorc: { rule: v => [1, 2].includes(v) },
        //Bluetooth prefix 
        blpri: { rule: v => typeof v != 'number' },
        //Bluetooth suffix
        blpos: { rule: v => typeof v != 'number' },
        //Bluetooth plus Enter 0 no enter 1 enter
        blcr: { rule: v => [0, 1].includes(v) },
        //Bluetooth adds a newline character; 0 doesn't add a newline character, 1 adds a newline character.
        blnl: { rule: v => [0, 1].includes(v) },
        //Bluetooth name
        ble_name: { rule: v => typeof v == 'string', callback: v => driver.uartBle.setConfig({ name: v }) },
        //Bluetooth Mac
        ble_mac: { rule: v => typeof v == 'string', callback: v => driver.uartBle.setConfig({ mac: v }) },
        //Bluetooth output format 0 Direct output
        blft: { rule: regnCheck },
        //Power-on beep - 0 (Off)
        boot_music: { rule: v => [0, 1].includes(v) },
        //Voice volume 0-60
        volume: { rule: regnCheck, callback: v => driver.audio.volume(Math.floor(v / 10)) },
        //Buzzer volume
        volume1: { rule: regnCheck },
        //LCD backlight brightness
        backlight: {
            rule: (v) => {
                return regnCheck(v) && v <= 100
            }, callback: v => common.systemBrief("echo " + (v <= 0 ? 1 : Math.floor(v * 15 / 100)) + " > /sys/class/backlight/backlight/brightness")
        },
        //White fill light brightness
        brightness: { rule: () => true },
        //userId
        userid: { rule: v => typeof v == 'string' },
        //Device number
        devnum: { rule: regnCheck },
        //Equipment Name
        devname: { rule: v => typeof v == 'string', callback: v => driver.screen.reload() },

        //Heartbeat switch: 0 Off, 1 On
        heart_en: { rule: v => [0, 1].includes(v), callback: v => bus.fire("heartbeat", { heart_en: v }) },
        //Heartbeat content
        heart_data: { rule: v => typeof v == 'string', callback: v => bus.fire("heartbeat", { heart_data: v }) },
        //Heartbeat interval s
        heart_time: { rule: regnCheck, callback: v => bus.fire("heartbeat", { heart_time: v }) },
        //Hide SN? 0 Hide 1 Show
        sn_show: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        //Hide IP? 0 Hide 1 Show
        ip_show: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        //Screen rotation
        rotation: { rule: v => [0, 1, 2, 3].includes(v), callback: v => rotation(v) },
        //Language: here 0 and 1 English
        language: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        // Background image
        rotation0BgImage: { rule: v => typeof v == 'string', callback: v => driver.screen.reload() },
        rotation1BgImage: { rule: v => typeof v == 'string', callback: v => driver.screen.reload() },
        rotation2BgImage: { rule: v => typeof v == 'string', callback: v => driver.screen.reload() },
        rotation3BgImage: { rule: v => typeof v == 'string', callback: v => driver.screen.reload() },
        //Configure password. Default is 1234567887654321
        com_passwd: { rule: v => typeof v == 'string' },
        //NTP time synchronization: 0 - Disabled, 1 - Enabled
        ntp_en: { rule: v => [0, 1].includes(v) },
        //The default time synchronization server is 182.92.12.11.
        ntp_addr: { rule: ipCheck },
        //The default port is 123.
        ntp_port: { rule: port },
        //NTP synchronization timeout (default 5 seconds)
        ntp_timeout: { rule: regnCheck },
        //The default NTP synchronization interval is 43200 seconds.
        ntp_interval: { rule: regnCheck },
        //NTP fixed-time synchronization, scheduled synchronization, 23 means automatic synchronization time at 23:00 every day.
        ntp_hour: { rule: regnCheck },
        //Version number
        version: { rule: v => typeof v == 'string' },
        version_show: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        //Which interface is used for protocol reporting?
        //Reporting mode: 00 represents the host computer polling 0x30 to obtain barcode scanner data; 01 represents selecting the 0x30 command to actively report data; 80 represents the host computer polling 0x33 to obtain scanner data; 0x81 represents selecting the 0x33 command to actively report data.
        report_mode: { rule: v => v == '1' || v == '0' || v == '81' || v == '80' },
        //The default data validity period for the barcode scanner is 2000ms.
        report_timeout: { rule: regnCheck },
        // Password button 1: On 0: Off
        show_unlocking: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        // Button display text
        buttonText: { rule: v => typeof v == 'string' && v.length <= 6, callback: v => driver.screen.reload() },
        // Time zone
        ntpLocaltime: { rule: regnCheck, callback: v => dxNtp.updateGmt(v) },
        // Safety module switch
        safe_open: { rule: v => [0, 1, 2].includes(v), callback: v => safeService.updateSafeOpen(v) },

        safe_keytype: { rule: v => [1].includes(v) },
        //Security Module Key
        safe_key_pub: { rule: v => typeof v == 'string' },
        //Security Module Key IV
        safe_iv_pri: { rule: v => typeof v == 'string' },
        // Date Show/Hide 1 On 0 Off
        show_date: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        // Device Name Show/Hide: 1 On, 0 Off
        show_devname: { rule: v => [0, 1].includes(v), callback: v => driver.screen.reload() },
        //WiFi username
        ssid: { rule: v => typeof v == 'string' },
        //wifi password
        psk: { rule: v => typeof v == 'string' },
        reboot: { rule: () => true },
        // Set system time, in seconds.
        time: { rule: regnCheck, callback: v =>setTime(v) },
        dateFormat: { rule: v => [1, 2].includes(v), callback: v => driver.screen.timeFormat() },
        timeFormat: { rule: v => [1, 2].includes(v), callback: v => driver.screen.timeFormat() },
        nfcType: { rule: v => [1, 2, 3].includes(v) },
        sectorNumber: { rule: v => v === "" || (typeof v == 'number' && /^([0-9]|1[0-5])$/.test(v.toString()))},
        blockNumber: { rule: v => ["", 0, 1, 2].includes(v) },
        secretkeyType: { rule: v => [1, 2].includes(v) },
        secretkey: { rule: v => /^[0-9a-fA-F]{12}$/.test(v) },
        //-1 Disable automatic restart 0-23 Restart on the hour
        autoRestart: { rule: v => typeof v == 'number' && /^(-1|[0-9]|1[0-9]|2[0-3])$/.test(v.toString()) },
        timeZone: { rule: v => {
            if (typeof v !== 'string') return false
            const path = `${tz.root}${v}`
            if (!std.exist(path)) {
                return false
            }
            return true
        }, callback: v => {
            tz.updateTimeZone(v)
            tz.reboot()
        }},
    }
}
function rotation(rotation) {
    if (rotation == 0) {
        rotation = 1
    } else if (rotation == 1) {
        rotation = 0
    } else if (rotation == 2) {
        rotation = 3
    } else if (rotation == 3) {
        rotation = 2
    }
    config.set("sysInfo.rotation", rotation)
    driver.screen.reload()
}
// Configuration that needs to be restarted
const needReboot = ['owifi', 'w_mode', 'ntpLocaltime', 'codeSwitch', 'nfc', 'taddr', 'port', 'de_type',
    'net_type', 'ip_mode', 'ip', 'gateway', 'dns', 'mask', 'fixed_macaddr_enable', 'ntp_en', 'ntp_addr', 'ntp_port', 'ochannel', 'sysInfo.autoRestart']
    
// Unified User Configuration Validation Method
configService.configVerifyAndSave = function (data) {
    let isReboot = false
    let allPassed = true
    for (const key in data) {
        if (!supported[key]) {
            allPassed = false
            return key + " not supported"
        }
        const item = data[key];
        if (typeof item != 'object') {
            allPassed = false
            // It must be a group
            return
        }
        log.info("-------------" + key)
        for (const subKey in item) {
            let option = supported[key][subKey]
            if (utils.isEmpty(option)) {
                allPassed = false
                return subKey + " not supported"
            }
            const value = item[subKey];
            if (needReboot.includes(subKey)) {
                isReboot = true
            }
            //Security-related modules are discussed separately because modifying the key requires determining whether to save it to the config based on the return value. The switch needs special handling; when V=2, the operation should be saved as 1.
            if (subKey.includes("safe_")) {
                safeService.updateConfig(subKey, value);

            } else {
                if (option.rule && !option.rule(value)) {
                    allPassed = false
                    return subKey + ": " + value + " check failure"
                }
            }
        }
    }
    if(allPassed) {
        for (const key in data) {
            const item = data[key];
            for (const subKey in item) {
                let option = supported[key][subKey]
                const value = item[subKey];
                config.set(key + "." + subKey, value)
                if (option.callback) {
                    log.info("Execute configuration settings callback")
                    // Execute configuration settings callback
                    option.callback(value)
                }
            }
        }
    }
    config.save()
    // Checking for configurations that need to be restarted, restarting in 3 seconds.
    if (isReboot) {
        common.asyncReboot(3)
    }
    return true
}
function setTime(v) {
    common.systemBrief(`date -s "@${v}"`)
    driver.watchdog.feed("controller", 30)
    driver.watchdog.feed("main", 30)
    let map = dxMap.get('workerId')
    let workerIds = map.get('workerId')
    for (let element of workerIds) {
        driver.watchdog.feed(element, 30)
    }
    driver.screen.reload()

}
export default configService
